﻿using System;
using System.Collections.Generic;
using System.Text;

namespace EnCor.ObjectBuilder
{
    /// <summary>
    /// Represents a generic process to build objects of a polymorphic hierarchy based on a single configuration object.
    /// </summary>
    /// <remarks>
    /// This custom factory will rely assemblers to do the actual building out for a concrete type. Assemblers are implementations of the 
    /// <see cref="IAssembler{TObject, TConfiguration}"/> interface. Concrete configuration objects must have an 
    /// <see cref="AssemblerAttribute">Assembler</see> attribute to allow the factory to determine how the configuration should be interpreted.
    /// </remarks>
    /// <typeparam name="TObject">The interface or the base type to build.</typeparam>
    /// <typeparam name="TConfiguration">The base configuration object type.</typeparam>
    public abstract class AssemblerBasedObjectFactory<TObject, TConfiguration>
        where TObject : class
        where TConfiguration : class
    {

        /// <summary>
        /// This method supports the Enterprise Library infrastructure and is not intended to be used directly from your code.
        /// Returns a new instance of a concrete subtype of <typeparamref name="TObject"/>, described by the matching configuration object 
        /// of a concrete subtype of <typeparamref name="TConfiguration"/> in <paramref name="objectConfiguration"/>.
        /// </summary>
        /// <param name="context">The <see cref="IBuilderContext"/> that represents the current building process.</param>
        /// <param name="objectConfiguration">The configuration object that describes the object to build.</param>
        /// <param name="configurationSource">The source for configuration objects.</param>
        /// <param name="reflectionCache">The cache to use retrieving reflection information.</param>
        /// <returns>A new instance of the appropriate subtype of <typeparamref name="Tobject"/>.</returns>
        public virtual TObject Create(IBuilderContext context, TConfiguration objectConfiguration)
        {
            IAssembler<TObject, TConfiguration> assembler = GetAssembler(objectConfiguration);
            TObject createdObject = assembler.Assemble(context, objectConfiguration);

            return createdObject;
        }

        protected virtual IAssembler<TObject, TConfiguration> GetAssembler(TConfiguration objectConfiguration)
        {
            Type type = objectConfiguration.GetType();
            IAssembler<TObject, TConfiguration> assembler;

            if (typeof(IAssembler<TObject, TConfiguration>).IsAssignableFrom(objectConfiguration.GetType()))
            {
                return objectConfiguration as IAssembler<TObject, TConfiguration>;
            }

            AssemblerAttribute assemblerAttribute = GetAssemblerAttribute(type);

            if (!typeof(IAssembler<TObject, TConfiguration>).IsAssignableFrom(assemblerAttribute.AssemblerType))
            {
                throw new InvalidOperationException();
            }

            assembler = (IAssembler<TObject, TConfiguration>)Activator.CreateInstance(assemblerAttribute.AssemblerType);

            return assembler;
        }

        private AssemblerAttribute GetAssemblerAttribute(Type type)
        {
            AssemblerAttribute assemblerAttribute
                = Attribute.GetCustomAttribute(type, typeof(AssemblerAttribute)) as AssemblerAttribute;
            if (assemblerAttribute == null)
            {
                throw new InvalidOperationException();
            }

            return assemblerAttribute;
        }
    }
}
